/*
** bootloader.c
** --
** This bootloader is part of Yablo project.
** See full documentation at http://code.google.com/p/yablo
** --
** (c) Mauricio Martins da Costa
** mauricio_martins@hotmail.com
** ALL RIGHTS RESERVED
*/
/*
** This program is provided as is. 
** The author is not and never will be responsible for any damage caused by using it.
** If you want to use it and embed it in your products, you're free to use. Just carry on 
** the credits of the author. I'll be glad helping you to succeed in your projects.
*/
/*
** References for this work:
** 	Microchip PIC18F252 datasheet: http://ww1.microchip.com/downloads/en/devicedoc/39564c.pdf
** 	AN851: http://ww1.microchip.com/downloads/en/appnotes/00851b.pdf
** 	AN774: http://ww1.microchip.com/downloads/en/appnotes/00774a.pdf
**  KEIL's Intel Hex File format: http://www.keil.com/support/docs/1584/
** --
** Remember: Datasheets are your friend! Read them thoroughly :-)
*/

#include <pic18fregs.h>

#pragma config OSC=HS, PWRT=OFF, BOR=OFF, WDT=OFF, CCP2MUX=OFF, LVP=OFF, CP0=OFF, CP1=OFF, CP2=OFF, CP3=OFF

unsigned char buffer[16];
unsigned char i, c, len, idx;
unsigned char addru=0x00;
unsigned char addrh=0x00;
unsigned char addrl=0x00;
unsigned char cksum,rtype;

#define XON 	0x11
#define XOFF 	0x13

void bootstrap (void);

void vecRs() __naked __interrupt 0 {
	__asm  GOTO _bootstrap __endasm;
}
void vecHi() __naked __interrupt 1 {
	__asm  GOTO 0x408 __endasm;
}
void vecLo() __naked __interrupt 2 {
	__asm  GOTO 0x418 __endasm;
}

/* putChar - write char to the USART
** 	input: unsigned char c - the char to be written
**	returns: none
*/
void putChar(unsigned char c) {
	while(!PIR1bits.TXIF);
	TXREG=c;
}

/* getTChar() - getChar with Timeout
** 	input: none
**	return: 0xFF if timeout occurs, otherwise the char in USART input.
*/
unsigned char getTChar() {
	unsigned char c,t;
	// setup timer
	T1CON=0x3C;//timer 1 off
	TMR1H=60;TMR1L=176; //0.1ms @16MHz
	t=50; // 5 seconds
	T1CON=0x3D;// timer 1 on
	while(!PIR1bits.RCIF && t) {
		if(PIR1bits.TMR1IF) { 
			t--;
			PIR1bits.TMR1IF=0;
			TMR1H=60;TMR1L=176; //0.1ms @16MHz
		}
	}

	T1CON=0x3C;

	if(t) {
		c= RCREG;
		PIR1bits.RCIF = 0;
	} else 	
		c=0xFF;

	return c;
}

/* getChar - read char from the USART
**	input: none
**	returns: the char read
*/
unsigned char getChar() {
	unsigned char c;
	
	while(!PIR1bits.RCIF);
	c= RCREG;
	PIR1bits.RCIF = 0;

	return c;
}

/* getNibble - read a nibble from USART
** 	input: none
**	returns: the char from space [0-9A-Fa-f]  converted to their respective value ranged from 0x00..0x0F
** 	remark: code optmized to avoid unecessary byte code generated by compiler.
*/
unsigned char getNibble() {
	unsigned char c=getChar();

	// turn bit 6 on (it does not affect numbers, 
	// but converts uppercase to lowercase)
	c|=0x20; 

	// instead using subtractions, 
	// better using aditions (+0xD0=-0x30; +0xA9=-'a'+10) :)
	return c<='9'? c+0xD0 : c+0xA9; 
}

/* getByte - read byte from USART
** 	input: none
**	returns: 
**		the byte collected from ASCII stream ('FF'-> 0xFF)
**	remark: computes checksum as specified by the IHX32 standard
*/
unsigned char getByte() {
	unsigned char temp=(getNibble()<<4);
	temp|=getNibble();
	cksum+=temp;
	return temp;
}

/* checkSum - verify checksum from the byte stream read from USART
**	input: none
**	return: none
** 	remark: 
**		The magic here is the following: while reading the 
**		stream of bytes the read checksum is calculated. 
**		The input's checksum is simply the complement of 2 
**		of the sum of the incoming bytes. IF I add this input 
**		checksum to the my calculated the final sum MUST BE ZERO! :)
*/
void checkSum() __naked { 
	getByte();  // read the checksum byte
	if(cksum) { // I need to create something better here... 
		    // resetting the uC won't help.
		__asm RESET __endasm;	
	}
	__asm RETURN __endasm;
}

/* zap - commit write to the memory
** 	input: none
** 	return: none
** 	remark: 
**		it requires before calling zap() to set 
**		the TBLPTR(U,H,L)+TBLAT (for PGM+CFG bits); or
**		EEADR+EEDATA for eeprom; and
**		the bit settings for 
			EECON1bits.EEPGD 
			EECON1bits.CFGS
	   		EECON1bits.FREE 
**		depending the case
*/
void zap(void) __naked  {
	EECON1bits.WREN=1;
	EECON2=0x55;
	EECON2=0xAA;
	EECON1bits.WR=1;
	__asm NOP __endasm;
	while(EECON1bits.WR);
	EECON1bits.WREN=0;
	__asm RETURN __endasm;
}

enum states {__IDLE, __READLINE, __END} state;

/* bootstrap - starts bootloader
** input: none
** return: none
** remark:
**	This bootloader is prepared for PIC18F252 
**	running with external Crystal (16MHZ)
** 	Its serial interface must be set to be running 
**	at 9600,8,N,1 with protocol XON/XOFF
**	The target program must have it's vector relocated to address 0x400 
**	This bootloader takes the first 0x3FF bytes, no interrupts cascaded.
*/
void bootstrap(void) __naked {

	// Init USART
	TRISCbits.TRISC7=1; //Make UART RX pin input
	TRISCbits.TRISC6=0; //Make UART TX pin output

	SPBRG=103;     //9600@16MHz Osc

	TXSTAbits.BRGH=1;
	TXSTAbits.SYNC=0;
	RCSTAbits.SPEN=1;
	RCSTAbits.CREN=1;
	RCSTAbits.SREN=1;

	PIE1bits.RCIE=0;
	PIE1bits.TXIE=0;
	PIR1bits.RCIF=0;

	TXSTAbits.TXEN=0;
	TXSTAbits.TXEN=1;

	INTCON1bits.GIE=0;

	state=__READLINE;
	while(state!=__END) {
		/* This is the most critical session. Any extra processing here, 
			may lead USART to fail due overrun error. So be quick! Don't add
			any extra processing here. Just collect the IHX row, and process 
			further

		** XON/XOFF handshaking - bootloader MUST indicate to the sender to START/STOP
			sending next data. Otherwise, next read cycle probably, we'll get 
			the damn OverRun error.
		** Note: EVEN when not loading the code, Serial port will be emiting XON character
			Make sure the connected device 	
		*/
		
		c=getTChar(); // silently wait for an input - 
		if(c==':') {
			cksum=0;
			len=getByte();
			addrh=getByte();
			addrl=getByte();
			rtype=getByte();
			for(i=0;i<len;i++) {
				buffer[i]=getByte();	
			}
			checkSum();
			putChar(XOFF); // stop receiving
		} else if (c==0xFF) { // quit if input timed out.
			state=__END;
			continue;
		} else { // discard any further character 
			continue;
		}
		
		// process fragment

		switch(rtype) {
			case 0x00:
				if(addru==0x30) { // config bits
					for(i=0;i<len;i++) {
						TBLPTRU=addru;
						TBLPTRH=addrh;
						TBLPTRL=addrl;
						TABLAT =buffer[i];					
	
						__asm tblwt* __endasm;
	
						EECON1bits.EEPGD=1;  // select for CONFIG
						EECON1bits.CFGS=1;
						zap();
						addrl++;
					}
				} else if(addru==0xf0) {
					for(i=0;i<len;i++) {
						EEADR=addrl;
	        				EEDATA=buffer[i];
	
					        EECON1bits.EEPGD=0;     // select for EEPROM
	        				EECON1bits.CFGS=0;
	        				zap();
						addrl++;
					}
				} else {
					TBLPTRU=addru;
	        			TBLPTRH=addrh;
	        			TBLPTRL=(addrl&0xF8);	
					for(i=0;i<len;i++) {
						TABLAT=buffer[i];
						__asm TBLWT*+ __endasm;
							
						if(((addrl&0x07)==0x07)||(len==0)){
							// rebind the addressing again to start of block
							// bug?
							TBLPTRU=addru;
	        					TBLPTRH=addrh;
	        					TBLPTRL=(addrl&0xF8);
								
							EECON1bits.EEPGD=1;     // select for PGM
	        					EECON1bits.CFGS=0;
	   						EECON1bits.FREE = 0;

	        					zap();
						}
						addrl++;
					}
				}	
				putChar(XON); /* more lines */
				break;
			case 0x01:
				state=__END;
				break;
			case 0x04:
				addru=buffer[1];
				putChar(XON); /* Ok! :) go to next row!*/
				break;
			default:
				__asm RESET __endasm;	
				break;
		}
	}
	// wooooooohay! :) code loaded? run it! 
	__asm GOTO 0x400 __endasm;
}
